// imports necessary for openGL
import processing.opengl.*;
import javax.media.opengl.*;

// import necessary for GLGraphics lib
import codeanticode.glgraphics.*;

// import the TUIO library for multitouch support
import TUIO.*;

// TWEAKABLE VARIABLES
int   screenWidth            = 1024;
int   screenHeight           = 768;
float scaleSize              = 100.0; // pixels
float scaleRotationAmount    = 0.0;
float scaleOverlapPercentX   = 0.30;
float scaleOverlapPercentY   = 0.75;
int   numScaleTextures       = 13;
float colorAmtScaleIdle      = 0.5;
float colorAmtScaleGlow      = 1.0;
float haloAmtScaleGlow       = 0.3;
float minGlowSpreadTarget    = 0.8;
float glowBranchChance       = 0.1;
float idleTimeBeforeHand     = 15.0; // seconds
float handHoverTime          = 1.5; // seconds
float handSize               = 240.0; // pixels
String handFilename          = "hand_full.png"; // ("hand_full.png" or "hand_finger.png")
//boolean useHaloEffect        = false;
boolean useHaloEffect        = true;
boolean useBloomShader       = true;
//boolean useBloomShader       = false;
boolean useWavyShader        = false;
boolean useDemo              = false;

// some important global vars
ScaleGrid      scaleGrid;
TuioProcessing tuioClient;
TouchGen       touchGen;
TouchHand      touchHand;
float          idleTimer = 0.0;

// high-level graphics objects
GL                   gl;
GLGraphicsOffScreen  glos;
GLSLShader           shaderBloom;
GLSLShader           shaderWavy;

// shader specific vars
int        locBloomColorMap  = 0;
int        locWavyColorMap  = 0;
int        elapsedTimeLoc = 0;

// variables to be uploaded to shader memory for application-shader data communication
float      elapsedTime = 0.0;
float      lastTime = 0.0;
float      deltaTime = 0.0;

void setup()
{
  // standard setup, but passing in GLGRAPHICS constant, which allows faster texture rendering under OpenGL
  size(screenWidth, screenHeight, GLConstants.GLGRAPHICS);
  if(useDemo) {
    touchGen  = new TouchGen(3, 2500.0,2500.0);
  }
  
  handTex = new GLTexture(this, handFilename);
  touchHand = new TouchHand();
  
  // initialize our TUIO client
  tuioClient = new TuioProcessing(this,20000);
  
  // initialize our off-screen rendering surface. the reason this is used is to do all our rendering
  // to a separate surface (or texture) and then in the final render pass, render the surface onscreen,
  // and apply all post-processing shaders, at once, to the final surface.
  glos  = new GLGraphicsOffScreen(this, screenWidth, screenHeight);

  // grab a hold of our OpenGL graphics context
  gl = ((PGraphicsOpenGL)g).gl;
  
  // some useful information to output
  println("OpenGL version: " + gl.glGetString(GL.GL_VERSION));
  println("GLSL version: " + gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION_ARB));
  
  // setup shaders
  if(useBloomShader == true)
  {
    shaderBloom = new GLSLShader(this);
    shaderBloom.loadFragmentShader("bloom.frag");
    //shaderBloom.loadVertexShader("bloom.vert");
    shaderBloom.linkProgram();
    
    locBloomColorMap    = shaderBloom.getUniformLocation("colorMap");
  }
  
  if(useWavyShader == true)
  {
    shaderWavy = new GLSLShader(this);
    shaderWavy.loadFragmentShader("wavy.frag");
    shaderWavy.linkProgram();
    
    locWavyColorMap     = shaderWavy.getUniformLocation("tex0");
    elapsedTimeLoc      = shaderWavy.getUniformLocation("elapsed_time");
  }
  
  // load resources
  for(int i = 0; i < numScaleTextures; i++)
  {
    scaleTex[i] = new GLTexture(this, "scale" + (i+1) +".png");
    //scaleTex[i] = new GLTexture(this, "scale.png");
  }
  scaleGlowTex = new GLTexture(this, "scale_glow.png");
  
  // create grid of scales
  scaleGrid = new ScaleGrid(screenWidth, screenHeight, scaleSize, scaleOverlapPercentX, scaleOverlapPercentY);
}

void update()
{
  // time-keeping
  lastTime    = elapsedTime;
  elapsedTime = (float)millis();
  deltaTime   = elapsedTime - lastTime;
  
  // construct array list to hold our touch points
  Vector points = new Vector();
  
  // grab all our TUIO cursors
  Vector tuioCursors = tuioClient.getTuioCursors();
  
  for( int i = 0; i < tuioCursors.size(); i++ )
  {
    TuioCursor tuioCursor = (TuioCursor)tuioCursors.elementAt(i);
    // grab its position and convert to our screen coordinates
    TuioPoint pos = tuioCursor.getPosition();
    
    points.add(new PVector(pos.getX() * width, pos.getY() * height));
  }
  
  if(useDemo)
  {
    // get points from our touch generator
    touchGen.update(deltaTime);
    points.addAll(touchGen.getTouches());
  }
  
  // add our mouse cursor to the points, if the mouse button is down
  if(mousePressed)
    points.add(new PVector(mouseX,mouseY));
    
  if(tuioCursors.size() == 0 && mousePressed == false)
  {
    // no touches, increment our idle timer;
    if(touchHand.isOn() == false)
    {
      idleTimer += deltaTime;
      if(idleTimer > idleTimeBeforeHand * 1000.0)
      {
        // idle timer expired, start the hand display
        idleTimer = 0.0;
        touchHand.start(width/2.0,height*3.0/4.0);
      }
    }
  }
  else
  {
    // there was a touch, test if hand is displayed
    if(touchHand.isOn())
      touchHand.stop();
  }
  
  if(touchHand.isOn())
  {
    PVector vecHand = touchHand.getTouch();
    if(vecHand != null)
      points.add(vecHand);
  }
  
  // update 'grid' of fishscales
  scaleGrid.update(deltaTime,points);
  
  // update hand display
  touchHand.update(deltaTime);
}

void draw()
{
  update();
  
  noCursor();
  background(0);
  
  tint(255,255,255,255);
  
  // retrieve our OpenGL rendering context, and begin rendering calls
  PGraphicsOpenGL pgl = (PGraphicsOpenGL) g;
  //gl = pgl.beginGL();
  gl = ((PGraphicsOpenGL)g).gl;
  
  //////////////////////////////////////////////////////////////
  // First render pass
  
  glos.beginDraw();
  
  glos.tint(255,255,255,255);

  // render our 'grid' of fishscales into the off screen buffer
  scaleGrid.render(glos);
  
  // bind our necessary textures to the appropriate texture units
  glos.gl.glActiveTexture(gl.GL_TEXTURE0);
  glos.gl.glEnable(gl.GL_TEXTURE_2D);
  glos.gl.glBindTexture( gl.GL_TEXTURE_2D, glos.getTexture().getTextureID() ); // color texture
  
  // begin bloom shader application
  if(useBloomShader == true)
  {
    shaderBloom.start();
    
    // upload our application variables to shader memory
    glos.gl.glUniform1i(locBloomColorMap,0);   // texture unit containing color texture
  }
  
  // render the off-screen's surface back onto itself, applying bloom shader
  glos.image( glos.getTexture(), 0, 0, width, height );
  
  if(useBloomShader == true)
    shaderBloom.stop();
    
    
  // begin wavy shader application
  if(useWavyShader == true)
  {
    shaderWavy.start();
    
    // upload our application variables to shader memory
    glos.gl.glUniform1i(locWavyColorMap,0);   // texture unit containing color texture
    gl.glUniform1f(elapsedTimeLoc,elapsedTime / 1000.0);
  
    // render the off-screen's surface back onto itself, applying bloom shader
    glos.image( glos.getTexture(), 0, 0, width, height );
    
    shaderWavy.stop();
  }
  
  glos.endDraw();
  
  // Final render pass
  
  // render off-screen surface on-screen
  image(glos.getTexture(),0,0);
  
  // render hand display
  touchHand.render();
  
  // conclude rendering calls
  //pgl.endGL();
}


// setup our TUIO callback functions

// called when an object is added to the scene
void addTuioObject(TuioObject tobj) {
}

// called when an object is removed from the scene
void removeTuioObject(TuioObject tobj) {
}

// called when an object is moved
void updateTuioObject (TuioObject tobj) {
}

// called when a cursor is added to the scene
void addTuioCursor(TuioCursor tcur) {
}

// called when a cursor is moved
void updateTuioCursor (TuioCursor tcur) {
  println("update cursor "+tcur.getCursorID()+" ("+tcur.getSessionID()+ ") " +tcur.getX()+" "+tcur.getY()
          +" "+tcur.getMotionSpeed()+" "+tcur.getMotionAccel());
}

// called when a cursor is removed from the scene
void removeTuioCursor(TuioCursor tcur) {
}

// called after each message bundle
// representing the end of an image frame
void refresh(TuioTime bundleTime) { 
  redraw();
}
